package com.sky.aspect;

import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;

@Component
@Slf4j
@Aspect
public class AutoFillAspect {
    @Pointcut("@annotation(com.sky.annotation.AutoFill)")
    public  void  autoFillPointCut(){
    }
    //前置通知为了在SQL语句在执行之前就完成对公共字段的赋值
    @Before("autoFillPointCut()")
    //通过joinpoint得到签名然后转换成方法签名对象
    //它返回一个表示连接点处的方法或构造函数签名的对象。这个对象可以是 MethodSignature 或者 ConstructorSignature 类型，
    // 具体取决于连接点的性质。
    public void  autoFill(JoinPoint joinPoint){
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //获得方法上的注解对象,注解的解析
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
        //获得数据库操作类型
        OperationType value = autoFill.value();
        //获取到当前被拦截的方法的参数--实体类对象
        Object[] args = joinPoint.getArgs();
        if (args==null ||args.length==0){
            return;
        }
        //默认这个数组的索引第一个索引就是存放这个实体类的
        Object arg = args[0];
        //准备赋值的数据
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();
        //根据当前不同的操作类型,为对应的属性通过反射来赋值
        if (value ==OperationType.INSERT){
            try {
                //getDeclaredMethod(String name, Class<?>... parameterTypes) 是 java.lang.Class 类中的一个方法，
                // 它返回一个 Method 对象，该对象反映了此 Class 对象所表示的类或接口中已声明的特定方法。
                //为这四个公共字段赋值
                /**
                 * arg.getClass().getDeclaredMethod 是 Java 反射 API 中的一个关键方法调用，用于获取由 arg 对象所属类中声明的方法。
                 * 具体来说，getDeclaredMethod 方法返回一个 Method 对象，它反映了此 Class 对象所表示的类或接口的指定已声明方法。
                 * 这里的关键点在于该方法仅返回那些直接在类中声明的方法，而不包括从父类或接口继承下来的方法
                 * ()第一个参数是方法名,第二个参数是方法的参数字节码对象,这样可以在重载方法中找到自己想要的方法
                 */
                Method setCreateTime = arg.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setCreateUser = arg.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateTime = arg.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = arg.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                //invoke方法自动执行将准好的数据赋值给实体类
                setCreateTime.invoke(arg,now);
                setCreateUser.invoke(arg,currentId);
                setUpdateTime.invoke(arg,now);
                setUpdateUser.invoke(arg,currentId);

            } catch (Exception e) {
                e.printStackTrace();
            }

        }else if (value==OperationType.UPDATE){
            try {
             Method setupdateTime =arg.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME,LocalDateTime.class);
             Method setupdateUser =arg.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER,Long.class);
             setupdateTime.invoke(arg,now);
             setupdateUser.invoke(arg,currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }
}
